﻿using System;
using System.Collections.Generic;
using System.Text;
using com.byt.webcore.utils;
using System.Security.Cryptography;
using System.Web.Security;

namespace com.byt.webcore.security
{
    public sealed class PasswordHasher
    {


        /**
         * Nombre del algoritmo de hash.
         */
        public static string ALGORITHM = "HmacSHA512";

        /**
         * Longitud del <code>salt</code>. Igual al tamaño del resultado del algoritmo.
         */
        public static int SALT_LENGTH = 64;

        /**
         * Número de iteraciones a realizar.
         */
        public static int ITERATIONS = 1000;

        /**
         * Nombre del conjunto de caracteres para codificar cadenas de texto.
         */
        public static string CHARSET_NAME = "UTF-8";

        /**
         * Representa el resultado de una operación de <code>hash</code> con <code>salt</code>.
         * 
         * @author Jose Manuel Cejudo Gausi
         */
        public sealed class PasswordHash
        {

            /**
             * Contiene el valor de <code>hash</code>.
             */
            private byte[] hash;

            /**
             * Contiene el valor de <code>salt</code>.
             */
            private byte[] salt;

            /**
             * Construye una nueva instancia con los valores indicados.
             * 
             * @param hash
             *            el valor de <code>hash</code>.
             * @param salt
             *            el valor de <code>salt</code>.
             */
            public PasswordHash(byte[] hash, byte[] salt)
            {

                this.hash = hash;
                this.salt = salt;

            }

            /**
             * Devuelve el valor de <code>hash</code>.
             * 
             * @return el valor.
             */
            public byte[] getHash()
            {

                return hash;

            }

            /**
             * Devuelve el valor de <code>hash</code> como string.
             * 
             * @return el valor.
             */
            public string getHashString()
            {

                return Utils.BytesToHex(hash);

            }

            /**
             * Devuelve el valor de <code>salt</code>.
             * 
             * @return el valor.
             */
            public byte[] getSalt()
            {

                return salt;

            }

            /**
             * Devuelve el valor de <code>salt</code> como string.
             * 
             * @return el valor.
             */
            public string getSaltString()
            {

                return Utils.BytesToHex(salt);

            }

        }

        /**
         * Constructor privado para impedir la instanciación de esta clase de utilidad.
         */
        private PasswordHasher()
            : base()
        {

        }

        /**
         * Calcula el <code>hash</code> del valor indicado.
         * <p>
         * Este método genera un valor de <code>salt</code> aleatorio y lo utiliza para calcular
         * repetitivamente tantas operaciones de <code>hash</code> como indique {@link #ITERATIONS}.
         * <p>
         * El objeto {@link PasswordHash}devuelto contiene el <code>hash</code> calculado y el
         * <code>salt</code> generado.
         * 
         * @param value
         * el valor para el que calcular el <code>hash</code>.
         * @return el resultado del cálculo <code>hash</code>.
         */
        public static PasswordHash hash(string value)
        {
            byte[] salt = new byte[SALT_LENGTH];

            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            rng.GetBytes(salt);

            HashAlgorithm algorithm = new SHA512Managed();
            byte[] h = algorithm.ComputeHash(salt);

            return hash(value, h);
        }

        /**
         * Calcula el <code>hash</code> del valor indicado usando el <code>salt</code> indicado.
         * <p>
         * El objeto {@link PasswordHash}devuelto contiene el <code>hash</code> calculado y el
         * <code>salt</code> generado.
         * 
         * @param value
         * el valor para el que calcular el <code>hash</code>.
         * @param salt
         * el valor de <code>salt</code> a utilizar.
         * @return el resultado del cálculo <code>hash</code>.
         */
        public static PasswordHash hash(string value, byte[] salt)
        {
            return hash(value, salt, "SHA512");
        }

        public static PasswordHash hash(string value, byte[] salt, string hashAlgorithm)
        {
            return hash(value, salt, hashAlgorithm, ITERATIONS);
        }

        public static PasswordHash hash(string value, byte[] salt, string hashAlgorithm, int iterations)
        {
            /*
            try {
                byte[] retVal;
                final byte[] valueBytes = value.getBytes(CHARSET_NAME);
                final Mac mac = Mac.getInstance(ALGORITHM);
                final Key key = new SecretKeySpec(salt, ALGORITHM);
                mac.init(key);
                retVal = mac.doFinal(valueBytes);
                for (int i = 1; i < ITERATIONS; i++) {
                    retVal = mac.doFinal(retVal);
                }
                return new PasswordHash(retVal, salt);

            } catch (final NoSuchAlgorithmException cause) {
                throw new RuntimeException(cause);
            } catch (final InvalidKeyException cause) {
                throw new RuntimeException(cause);
            } catch (final UnsupportedEncodingException cause) {
                throw new RuntimeException(cause);
            }
            */

            /*
            HMACSHA512 mac = new HMACSHA512(salt);
            byte[] valueBytes = Encoding.GetEncoding(CHARSET_NAME).GetBytes(value);
            byte[] retVal = mac.ComputeHash(valueBytes);
            for (int i = 1; i < ITERATIONS; i++)
            {
                retVal = mac.ComputeHash(retVal);
            }
            return new PasswordHash(retVal, salt);
             * */

            //System.Security.Cryptography.
            HashAlgorithm hash;

            switch (hashAlgorithm.ToUpper())
            {
                case "SHA1":
                    hash = new SHA1Managed();
                    break;

                case "SHA256":
                    hash = new SHA256Managed();
                    break;

                case "SHA384":
                    hash = new SHA384Managed();
                    break;

                case "SHA512":
                    hash = new SHA512Managed();
                    break;

                default:
                    hash = new MD5CryptoServiceProvider();
                    break;
            }

            byte[] valueBytes = Encoding.GetEncoding(CHARSET_NAME).GetBytes(value);
            byte[] retVal = hash.ComputeHash(valueBytes);
            for (int i = 1; i < iterations; i++)
            {
                retVal = hash.ComputeHash(retVal);
            }
            return new PasswordHash(retVal, salt);

        }

        /**
         * Comprueba si el <code>hash</code> calculado para el valor y el <code>salt</code> indicados es
         * igual al <code>hash</code> correcto indicado.
         * <p>
         * Este método permite comprobar si un valor es integro con respecto al <code>hash</code>.
         * 
         * @param value
         *            el valor a comprobar.
         * @param correctHash
         *            el <code>hash</code> correcto.
         * @param salt
         *            el <code>salt</code> a utilizar para la generación del <code>hash</code> de
         *            <code>value</code>. Debe ser el mismo que el utilizado para calcular
         *            <code>hash</code>.
         * @return <code>true</code> si el <code>hash</code> de <code>value</code> usando
         *         <code>salt</code> es igual a <code>correctHash</code>.
         */
        public static bool isValid(string value, byte[] correctHash, byte[] salt)
        {
            PasswordHash ph = hash(value, salt);
            return CompareByteArrays(ph.getHash(), correctHash);

        }

        public static bool CompareByteArrays(byte[] array1, byte[] array2)
        {
            if (array1.Length != array2.Length)
            {
                return false;
            }

            for (int i = 0; i < array1.Length; i++)
            {
                if (array1[i] != array2[i])
                {
                    return false;
                }
            }

            return true;
        }

        /**
         * Comprueba si el <code>hash</code> calculado para el valor y el <code>salt</code> indicados es
         * igual al <code>hash</code> correcto indicado.
         * <p>
         * Este método permite comprobar si un valor es integro con respecto al <code>hash</code>.
         * 
         * @param value
         *            el valor a comprobar.
         * @param correctHash
         *            el <code>hash</code> correcto.
         * @param salt
         *            el <code>salt</code> a utilizar para la generación del <code>hash</code> de
         *            <code>value</code>. Debe ser el mismo que el utilizado para calcular
         *            <code>hash</code>.
         * @return <code>true</code> si el <code>hash</code> de <code>value</code> usando
         *         <code>salt</code> es igual a <code>correctHash</code>.
         */
        public static bool isValid(string value, string correctHash, string salt)
        {
            PasswordHash ph;
            ph = hash(value, Utils.HexToBytes(salt));
            return ph.getHashString().Equals(correctHash);

        }


    }
}
